package visualizer.client.controller;

import com.google.gwt.animation.client.Animation;
import com.google.gwt.user.client.Random;
import com.google.gwt.user.client.Timer;
import visualizer.client.algorithms.*;
import visualizer.client.algorithms.bst.BSTAddingAlgorithm;
import visualizer.client.algorithms.bst.BSTRemovingAlgorithm;
import visualizer.client.algorithms.bst.BSTSearchingAlgorithm;
import visualizer.client.algorithms.rBst.RBSTAddingAlgorithm;
import visualizer.client.algorithms.rBst.RBSTRemovingAlgorithm;
import visualizer.client.algorithms.rBst.RBSTSearchingAlgorithm;
import visualizer.client.algorithms.rbBst.RBBSTAddingAlgorithm;
import visualizer.client.algorithms.rbBst.RBBSTRemovingAlgorithm;
import visualizer.client.algorithms.rbBst.RBBSTSearchingAlgorithm;
import visualizer.client.algorithms.sBst.SBSTAddingAlgorithm;
import visualizer.client.algorithms.sBst.SBSTRemovingAlgorithm;
import visualizer.client.algorithms.sBst.SBSTSearchingAlgorithm;
import visualizer.client.bst.BNode;
import visualizer.client.bst.BinaryTree;
import visualizer.client.utils.VisualizerUtils;
import visualizer.client.widgets.controlPanel.ControlPanelPresenter;
import visualizer.client.event.ControlPanelEventHandler;
import visualizer.client.widgets.messageBox.MessageBoxPresenter;
import visualizer.client.widgets.tree.TreePresenter;
import visualizer.client.utils.AlgorithmType;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;

import static visualizer.client.utils.AlgorithmType.*;

/**
 * User: DDovzhenko
 * Date: 05.01.14
 * Time: 19:24
 */
public class ShowController implements ControlPanelEventHandler {
    private final ControlPanelPresenter controlPanelPresenter;
    private final TreePresenter treePresenter;
    private MessageBoxPresenter messageBoxPresenter;
    private final Map<AlgorithmType, Algorithm> algorithms;
    private final StepTimer stepTimer = new StepTimer();

    private AlgorithmType selectedAlgorithm = AlgorithmType.BALANCE_WITH_VINE;
    private BinaryTree originalTree;
    private BinaryTree lastTree;
    private int stepNo = 0;
    private int stepsCount = 0;
    private boolean isStarted = false;
    private int inputValue;

    public ShowController(ControlPanelPresenter controlPanelPresenter, TreePresenter treePresenter, MessageBoxPresenter messageBoxPresenter) {
        this.controlPanelPresenter = controlPanelPresenter;
        this.treePresenter = treePresenter;
        this.messageBoxPresenter = messageBoxPresenter;

        algorithms = new HashMap<AlgorithmType, Algorithm>();
        fillAlgoMap();
    }

    private void fillAlgoMap() {
        algorithms.put(BST_ADDING, new BSTAddingAlgorithm());
        algorithms.put(BST_SEARCHING, new BSTSearchingAlgorithm());
        algorithms.put(BST_REMOVING, new BSTRemovingAlgorithm());
        algorithms.put(BALANCE_WITH_VINE, new DSWAlgorithm());
        algorithms.put(RANDOMIZE_BST_ADDING, new RBSTAddingAlgorithm());
        algorithms.put(RANDOMIZE_BST_SEARCHING, new RBSTSearchingAlgorithm());
        algorithms.put(RANDOMIZE_BST_REMOVING, new RBSTRemovingAlgorithm());
        algorithms.put(SPLAY_BST_ADDING, new SBSTAddingAlgorithm());
        algorithms.put(SPLAY_BST_SEARCHING, new SBSTSearchingAlgorithm());
        algorithms.put(SPLAY_BST_REMOVING, new SBSTRemovingAlgorithm());
        algorithms.put(RED_BLACK_BST_ADDING, new RBBSTAddingAlgorithm());
        algorithms.put(RED_BLACK_BST_SEARCHING, new RBBSTSearchingAlgorithm());
        algorithms.put(RED_BLACK_BST_REMOVING, new RBBSTRemovingAlgorithm());
    }

    public void setAlgorithm(AlgorithmType selectedAlgorithm) {
        endPreviousAlgorithm();
        stepNo = 0;
        isStarted = false;
        controlPanelPresenter.setMessage(selectedAlgorithm.getDescription());

        switch (selectedAlgorithm) {
            case BALANCE_WITH_VINE: {
                this.selectedAlgorithm = selectedAlgorithm;
                originalTree = new BinaryTree();
                for (int i = 0; i < 12; i++) {
                    originalTree.insertBST(Random.nextInt(100));
                }
                lastTree = originalTree.copyTree(false);
                prepareNewAlgorithm(0);
                stepNo--;
                break;
            }

            case RED_BLACK_BST_ADDING:
            case RED_BLACK_BST_SEARCHING:
            case RED_BLACK_BST_REMOVING: {
                if (this.selectedAlgorithm != RED_BLACK_BST_ADDING
                        && this.selectedAlgorithm != RED_BLACK_BST_SEARCHING
                        && this.selectedAlgorithm != RED_BLACK_BST_REMOVING)
                {
                    makeRedBlackTree();
                }
                break;
            }

            default:
                originalTree = originalTree.copyTree(false);
                break;
        }
        this.selectedAlgorithm = selectedAlgorithm;
        treePresenter.setModel(originalTree.copyTree(true));
    }

    private void makeRedBlackTree() {
        BNode root = originalTree.getRoot();
        if (root == null) {
            return; //tree is empty
        }
        BinaryTree redBlackTree = new BinaryTree();
        Algorithm algorithm = algorithms.get(RED_BLACK_BST_ADDING);
        Queue<BNode> queue = new LinkedList<BNode>();
        queue.add(root);
        while (!queue.isEmpty()) {
            BNode x = queue.poll();
            int stepCount = algorithm.getStepsCount(redBlackTree, x.getValue());
            AlgoResult algoResult = algorithm.performAlgorithmToStep(redBlackTree, stepCount - 1, x.getValue());
            redBlackTree = algoResult.getTree();
            if (x.getLeftBNode() != null) {
                queue.add(x.getLeftBNode());
            }
            if (x.getRightBNode() != null) {
                queue.add(x.getRightBNode());
            }
        }
        originalTree = redBlackTree;
    }

    @Override
    public void onPrevStepClick() {
        if (!isStarted || stepNo == 0) return;
        stepNo--;
        updateViews();
    }

    @Override
    public void onNextStepClick() {
        if (!isStarted || stepNo == stepsCount - 1) return;
        stepNo++;
        updateViews();
    }

    @Override
    public void onToStartClick() {
        if (!isStarted) return;
        stepNo = 0;
        updateViews();
    }

    @Override
    public void onToEndClick() {
        if (!isStarted) return;
        stepNo = stepsCount - 1;
        updateViews();
    }

    @Override
    public void onBeginClick(int value) {
        prepareNewAlgorithm(value);
        updateViews();
    }

    @Override
    public void onAnimationSpeedChange(int speed) {
        VisualizerUtils.animationSpeed = 400 + 8 * (100 - speed);
    }

    @Override
    public void onAnimationChange(boolean isStarted) {
        VisualizerUtils.isAnimationStarted = isStarted;
        if (isStarted) {
            VisualizerUtils.animationSpeed = 400 + 8 * (100 - controlPanelPresenter.getAnimationSpeed());
            stepTimer.schedule(VisualizerUtils.animationSpeed);
        } else {
            VisualizerUtils.animationSpeed = 1500;
            stepTimer.cancel();
        }
    }

    private void prepareNewAlgorithm(int value) {
        endPreviousAlgorithm();
        isStarted = true;
        stepNo = 0;
        this.inputValue = value;
        stepsCount = algorithms.get(selectedAlgorithm).getStepsCount(originalTree.copyTree(true), inputValue);
    }

    private void updateViews() {
        AlgoResult result = algorithms.get(selectedAlgorithm).performAlgorithmToStep(originalTree.copyTree(true), stepNo, inputValue);
        treePresenter.updateModel(result.getTree());
        controlPanelPresenter.setMessage(result.getStepDescription());
        lastTree = result.getTree();
        if (stepNo == stepsCount - 1) {
            messageBoxPresenter.setMessage(result.getStepDescription());
            messageBoxPresenter.show();
        }
    }

    private void endPreviousAlgorithm() {
        if (stepNo == stepsCount - 1) {
            originalTree = lastTree.copyTree(true);
        }
    }

    private class StepTimer extends Timer {

        @Override
        public void run() {
            if (!isStarted || stepNo == stepsCount - 1) {
                controlPanelPresenter.setAnimation(false);
            } else {
                stepNo++;
                updateViews();
                stepTimer.schedule(VisualizerUtils.animationSpeed);
                System.out.println(VisualizerUtils.animationSpeed);
            }
        }
    }
}
